﻿// <copyright file="KinectCursor.cs" company="Ucaya">
// </copyright>
// <author>Ucaya</author>

namespace KinectExtensions.Controls
{
    #region

    using System;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Media;
    using System.Windows.Media.Animation;

    using KinectExtensions.Helpers;
    using KinectExtensions.Nui;

    using Microsoft.Research.Kinect.Nui;

    #endregion

    /// <summary>
    ///   TODO: Update summary.
    /// </summary>
    public class KinectCursor : Control, ICursor
    {
        #region Constants and Fields

        /// <summary>
        ///   HoveringDuration depedency property.
        /// </summary>
        public static readonly DependencyProperty HoveringDurationProperty = DependencyProperty.Register("HoveringDuration", typeof(TimeSpan), typeof(KinectCursor), new PropertyMetadata(TimeSpan.FromSeconds(8), (d, e) => ((KinectCursor)d).OnHoveringDurationChanged((TimeSpan)e.OldValue, (TimeSpan)e.NewValue)));

        /// <summary>
        ///   IsActive depedency property.
        /// </summary>
        public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register("IsActive", typeof(bool), typeof(KinectCursor), new UIPropertyMetadata(false, (d, e) => ((KinectCursor)d).OnIsActiveChanged((bool)e.OldValue, (bool)e.NewValue)));

        /// <summary>
        ///   IsHovering depedency property.
        /// </summary>
        public static readonly DependencyProperty IsHoveringProperty = DependencyProperty.Register("IsHovering", typeof(bool), typeof(KinectCursor), new PropertyMetadata((d, e) => ((KinectCursor)d).OnIsHoveringChanged((bool)e.OldValue, (bool)e.NewValue)));

        /// <summary>
        ///   IsMaster depedency property.
        /// </summary>
        public static readonly DependencyProperty IsMasterProperty = DependencyProperty.Register("IsMaster", typeof(bool), typeof(KinectCursor), new PropertyMetadata((d, e) => ((KinectCursor)d).OnIsMasterChanged((bool)e.OldValue, (bool)e.NewValue)));

        /// <summary>
        ///   JointId depedency property.
        /// </summary>
        public static readonly DependencyProperty JointIdProperty = DependencyProperty.Register("JointId", typeof(JointID), typeof(KinectCursor), new PropertyMetadata((d, e) => ((KinectCursor)d).OnJointIdChanged((JointID)e.OldValue, (JointID)e.NewValue)));

        /// <summary>
        ///   KinectZone depedency property.
        /// </summary>
        public static readonly DependencyProperty KinectZoneProperty = DependencyProperty.Register("KinectZone", typeof(IKinectZone), typeof(KinectCursor), new PropertyMetadata((d, e) => ((KinectCursor)d).OnKinectZoneChanged((IKinectZone)e.OldValue, (IKinectZone)e.NewValue)));

        /// <summary>
        ///   Position depedency property.
        /// </summary>
        public static readonly DependencyProperty PositionProperty = DependencyProperty.Register("Position", typeof(Point), typeof(KinectCursor), new PropertyMetadata((d, e) => ((KinectCursor)d).OnPositionChanged((Point)e.OldValue, (Point)e.NewValue)));

        /// <summary>
        ///   VisualPosition depedency property.
        /// </summary>
        public static readonly DependencyProperty VisualPositionProperty = DependencyProperty.Register("VisualPosition", typeof(Point), typeof(KinectCursor), new PropertyMetadata((d, e) => ((KinectCursor)d).OnVisualPositionChanged((Point)e.OldValue, (Point)e.NewValue)));

        /// <summary>
        ///   The root layout.
        /// </summary>
        private FrameworkElement rootLayout;

        /// <summary>
        ///   The translate transform.
        /// </summary>
        private TranslateTransform translateTransform;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        ///   Initializes static members of the <see cref = "KinectCursor" /> class.
        /// </summary>
        static KinectCursor()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(KinectCursor), new FrameworkPropertyMetadata(typeof(KinectCursor)));
        }

        /// <summary>
        ///   Initializes a new instance of the <see cref = "KinectCursor" /> class.
        /// </summary>
        public KinectCursor()
        {
            this.translateTransform = new TranslateTransform();
        }

        #endregion

        #region Public Properties

        /// <summary>
        ///   Gets or sets HoveringDuration.
        /// </summary>
        public TimeSpan HoveringDuration
        {
            get
            {
                return (TimeSpan)this.GetValue(HoveringDurationProperty);
            }

            set
            {
                this.SetValue(HoveringDurationProperty, value);
            }
        }

        /// <summary>
        ///   Gets or sets a value indicating whether IsActive.
        /// </summary>
        public bool IsActive
        {
            get
            {
                return (bool)this.GetValue(IsActiveProperty);
            }

            set
            {
                this.SetValue(IsActiveProperty, value);
            }
        }

        /// <summary>
        ///   Gets or sets a value indicating whether IsHovering.
        /// </summary>
        public bool IsHovering
        {
            get
            {
                return (bool)this.GetValue(IsHoveringProperty);
            }

            set
            {
                this.SetValue(IsHoveringProperty, value);
            }
        }

        /// <summary>
        ///   Gets or sets a value indicating whether IsMaster.
        /// </summary>
        public bool IsMaster
        {
            get
            {
                return (bool)this.GetValue(IsMasterProperty);
            }

            set
            {
                this.SetValue(IsMasterProperty, value);
            }
        }

        /// <summary>
        ///   Gets or sets JointId.
        /// </summary>
        public JointID JointId
        {
            get
            {
                return (JointID)this.GetValue(JointIdProperty);
            }

            set
            {
                this.SetValue(JointIdProperty, value);
            }
        }

        /// <summary>
        ///   Gets or sets KinectZone.
        /// </summary>
        public IKinectZone KinectZone
        {
            get
            {
                return (IKinectZone)this.GetValue(KinectZoneProperty);
            }

            set
            {
                this.SetValue(KinectZoneProperty, value);
            }
        }

        /// <summary>
        ///   Gets Location.
        /// </summary>
        public Point Location
        {
            get
            {
                return this.VisualPosition;
            }
        }

        /// <summary>
        ///   Gets or sets Position.
        /// </summary>
        public Point Position
        {
            get
            {
                return (Point)this.GetValue(PositionProperty);
            }

            set
            {
                this.SetValue(PositionProperty, value);
            }
        }

        /// <summary>
        ///   Gets or sets VisualPosition.
        /// </summary>
        public Point VisualPosition
        {
            get
            {
                return (Point)this.GetValue(VisualPositionProperty);
            }

            set
            {
                this.SetValue(VisualPositionProperty, value);
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        ///   The create default move storyboard.
        /// </summary>
        /// <param name = "visualPosition">
        ///   The visual position.
        /// </param>
        /// <returns>
        /// </returns>
        public Storyboard CreateDefaultMoveStoryboard(Point visualPosition)
        {
            var sb = new Storyboard();
            var da = new DoubleAnimation();
            sb.Children.Add(da);
            Storyboard.SetTarget(da, this);
            Storyboard.SetTargetProperty(da, new PropertyPath("(FrameworkElement.RenderTransform).(TranslateTransform.X)"));
            da.To = visualPosition.X;
            da.Duration = TimeSpan.FromMilliseconds(50);

            da = new DoubleAnimation();
            sb.Children.Add(da);
            Storyboard.SetTarget(da, this);
            Storyboard.SetTargetProperty(da, new PropertyPath("(FrameworkElement.RenderTransform).(TranslateTransform.Y)"));
            da.To = visualPosition.Y;
            da.Duration = TimeSpan.FromMilliseconds(50);

            da = new DoubleAnimation();
            sb.Children.Add(da);
            Storyboard.SetTarget(da, this);
            Storyboard.SetTargetProperty(da, new PropertyPath("Opacity"));
            da.To = 1;
            da.Duration = TimeSpan.Zero;

            return sb;
        }

        /// <summary>
        ///   The get bounds.
        /// </summary>
        /// <returns>
        /// </returns>
        public Rect GetBounds()
        {
            return CustomVisualTreeHelper.GetHitableDescendantBounds(this);
        }

        /// <summary>
        ///   The on apply template.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            var tt = this.RenderTransform as TranslateTransform;
            if (tt == null)
            {
                this.RenderTransform = this.translateTransform;
            }
            else
            {
                this.translateTransform = tt;
            }

            this.rootLayout = this.GetTemplateChild("root") as FrameworkElement;
            this.RefreshJointIdState();
            this.RefreshActiveState();
            this.RefreshMasterState();
        }

        #endregion

        #region Methods

        /// <summary>
        ///   handles the HoveringDurationProperty changes.
        /// </summary>
        /// <param name = "oldValue">
        ///   The old value.
        /// </param>
        /// <param name = "newValue">
        ///   The new value.
        /// </param>
        private void OnHoveringDurationChanged(TimeSpan oldValue, TimeSpan newValue)
        {
        }

        /// <summary>
        ///   handles the IsActiveProperty changes.
        /// </summary>
        /// <param name = "oldValue">
        ///   The old value.
        /// </param>
        /// <param name = "newValue">
        ///   The new value.
        /// </param>
        private void OnIsActiveChanged(bool oldValue, bool newValue)
        {
            this.RefreshActiveState();
        }

        /// <summary>
        ///   handles the IsHoveringProperty changes.
        /// </summary>
        /// <param name = "oldValue">
        ///   The old value.
        /// </param>
        /// <param name = "newValue">
        ///   The new value.
        /// </param>
        private void OnIsHoveringChanged(bool oldValue, bool newValue)
        {
            VisualStateManager.GoToState(this, newValue ? "HoveringState" : "IdleState", true);
        }

        /// <summary>
        ///   handles the IsMasterProperty changes.
        /// </summary>
        /// <param name = "oldValue">
        ///   The old value.
        /// </param>
        /// <param name = "newValue">
        ///   The new value.
        /// </param>
        private void OnIsMasterChanged(bool oldValue, bool newValue)
        {
            this.RefreshMasterState();
        }

        /// <summary>
        ///   handles the JointIdProperty changes.
        /// </summary>
        /// <param name = "oldValue">
        ///   The old value.
        /// </param>
        /// <param name = "newValue">
        ///   The new value.
        /// </param>
        private void OnJointIdChanged(JointID oldValue, JointID newValue)
        {
            this.RefreshJointIdState();
        }

        /// <summary>
        ///   handles the KinectZoneProperty changes.
        /// </summary>
        /// <param name = "oldValue">
        ///   The old value.
        /// </param>
        /// <param name = "newValue">
        ///   The new value.
        /// </param>
        private void OnKinectZoneChanged(IKinectZone oldValue, IKinectZone newValue)
        {
            var hoverZone = newValue as HoverKinectZone;
            this.ClearValue(IsHoveringProperty);
            if (hoverZone != null)
            {
                if (this.rootLayout != null)
                {
                    Storyboard sb = (from g in VisualStateManager.GetVisualStateGroups(this.rootLayout).OfType<VisualStateGroup>()
                                     where g.Name == "HoveringStates"
                                     from s in g.States.OfType<VisualState>()
                                     where s.Name == "HoveringState"
                                     select s.Storyboard).FirstOrDefault();
                    if (sb != null)
                    {
                        sb.Duration = hoverZone.HoverActionDelayDuration;
                        DoubleAnimation da = sb.Children.OfType<DoubleAnimation>().FirstOrDefault();
                        da.Duration = hoverZone.HoverActionDelayDuration;
                    }
                }

                this.SetBinding(IsHoveringProperty, new Binding(hoverZone.GetPropertyName(_ => _.IsWaiting)) { Source = hoverZone });
            }
        }

        /// <summary>
        ///   handles the PositionProperty changes.
        /// </summary>
        /// <param name = "oldValue">
        ///   The old value.
        /// </param>
        /// <param name = "newValue">
        ///   The new value.
        /// </param>
        private void OnPositionChanged(Point oldValue, Point newValue)
        {
            Window h = Application.Current.MainWindow;
            if (h == null)
            {
                return;
            }

            this.VisualPosition = new Point(h.ActualWidth * newValue.X, h.ActualHeight * newValue.Y);
        }

        /// <summary>
        ///   handles the VisualPositionProperty changes.
        /// </summary>
        /// <param name = "oldValue">
        ///   The old value.
        /// </param>
        /// <param name = "newValue">
        ///   The new value.
        /// </param>
        private void OnVisualPositionChanged(Point oldValue, Point newValue)
        {
            if (this.KinectZone == null || !this.KinectZone.HandleCursorMove(this, this.translateTransform, newValue))
            {
                Storyboard sb = this.CreateDefaultMoveStoryboard(newValue);
                sb.Begin();
            }
        }

        /// <summary>
        ///   The refresh active state.
        /// </summary>
        private void RefreshActiveState()
        {
            VisualStateManager.GoToState(this, this.IsActive ? "ActiveState" : "NotActiveState", true);
        }

        /// <summary>
        ///   The refresh joint id state.
        /// </summary>
        private void RefreshJointIdState()
        {
            VisualStateManager.GoToState(this, this.JointId + "State", true);
        }

        /// <summary>
        ///   The refresh master state.
        /// </summary>
        private void RefreshMasterState()
        {
            VisualStateManager.GoToState(this, this.IsMaster ? "MasterState" : "SlateState", true);
        }

        #endregion
    }
}